/*******************************************************************************
 * Copyright (c) 2002, 2003 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.core.internal;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.PerforceException;
import net.sourceforge.perforce.core.PerforceSynchronizer;
import net.sourceforge.perforce.core.api.ICountersResult;
import net.sourceforge.perforce.core.api.IDescribeResult;
import net.sourceforge.perforce.core.api.IOpenedResult;
import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.api.IPerforceServerManager;
import net.sourceforge.perforce.core.api.IWhereResult;
import net.sourceforge.perforce.core.resources.IChangelist;
import net.sourceforge.perforce.core.syncinfo.ResourceSyncInfo;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;

/**
  * @version $Revision: 240 $
  * @author  <a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  */
public class BackgroundUpdateRunnable implements Runnable 
{
	// --------------------------------------------------------------------------- Private Variables

	/** Stores the Threadthat is running the monitor. */
	private Thread thread;
		
	/** Indicates whether the "Background Update Daemon" is running. */
	private boolean running = true;
	
	/** Maps the servername to its counter value. */
	private Map counterMap = new HashMap();
	
	// ------------------------------------------------------------------------------ Public Methods
	
	/** @see java.lang.Runnable#run() */
	public void run() 
	{
		thread = Thread.currentThread();
		
		IPerforceServerManager manager = PerforceCorePlugin.getServerManager();
		while (running)
		{
			final IPerforceServer[] servers = manager.getPerforceServers();
			for (int i = 0; i < servers.length; i++)
			{
				final IPerforceServer server = servers[i];
	
				if (server.shouldIncludeInBackgroundUpdates())
				{
					int lastChangelist = getLastChangelistNumber(server);
					int currentChangelist = getCurrentChangelistNumber(server);
	
					// Collect files that have changed because a changelist was submitted
					Set affectedResources = new HashSet();
					
					if (PerforceCorePlugin.getPlugin().shouldMonitorChangelists())
					{
						for (int j = lastChangelist + 1; j <= currentChangelist; j++)
						{
							affectedResources.addAll(getAffectedResources(server, j));
						}
					}
					
					// Collect files that are opened on this client
					if (PerforceCorePlugin.getPlugin().shouldMonitorOpenedFiles())
					{
						affectedResources.addAll(collectOpenedResources(server));
					}
					
					Map resourceMap = new HashMap();
					for (Iterator it = affectedResources.iterator(); it.hasNext();)
					{
						try
						{
							IResource resource = (IResource) it.next();
							ResourceSyncInfo[] syncInfo = 
								server.fstat(resource.getLocation().toOSString(), null);
							if (syncInfo != null && syncInfo.length == 1)
							{							
								resourceMap.put(resource, syncInfo[0]);
							}
						}
						catch (PerforceException e)
						{
							PerforceCorePlugin.log(e.getStatus());
						}
					}
					
					updateCache(resourceMap);
				}
			}
			
			try
			{
				Thread.sleep(PerforceCorePlugin.getPlugin().getBackgroundUpdateInterval() * 1000);
			}
			catch (InterruptedException e)
			{
				// Ignored!
			}
		}
	}
	
	/** Call this method to start the Background Update Monitor. */ 
	public void startup()
	{
		new Thread(this, "Perforce Background Update Monitor").start();                //$NON-NLS-1$
	}
	
	/** Call this method to shutdown the connection monitor. */
	public void shutdown()
	{
		running = false;
		try 
		{
			thread.interrupt();
			thread.join();
		} 
		catch (InterruptedException e) 
		{
			// Ignored!
		}
	}

	// ----------------------------------------------------------------------------- Private Methods
	
	/** Returns the number of the changelist we synchronized last.
	  *
	  * @param server the perforce server. 
	  * 
	  * @return number of last synchronized changelist.
	  */
	private int getLastChangelistNumber(IPerforceServer server)
	{
		String key = server.getUniqueName();
		
		Integer result = (Integer) counterMap.get(key);
		if (result == null)
		{
			result = new Integer(getCurrentChangelistNumber(server));
			counterMap.put(key, result);
		}
		
		return result.intValue();
	}

	/** Retrieves the current (server-side) changelist number.
	  *
	  * @param server the perforce server.
	  * 
	  * @return the changelist number. 
	  */
	private int getCurrentChangelistNumber(IPerforceServer server) 
	{
		try
		{
			ICountersResult countersResult = server.counters();
			
			Integer result = countersResult.getCounterValue("change");
			return (result != null) ? result.intValue() : 0;
		}
		catch (PerforceException e)
		{
			PerforceCorePlugin.log(e.getStatus());
			return 0;
		}
	}	

	/** Returns the resources that are affected by the given changelist.
	  * 
	  * @param server the perforce server.
	  * @param changelist the changelist number.
	  * @return set of resources.
	  */
	private Set getAffectedResources(IPerforceServer server, int changelist) 
	{
		Set result = new HashSet();
		try
		{
			IDescribeResult describeResult = server.describe(new Integer(changelist));
			if (describeResult.getStatus().equals(IChangelist.STATUS_PENDING))
			{
				return result;
			}
			
			// Collect depot filenames
			ResourceSyncInfo[] affectedFiles = describeResult.getAffectedFiles();
			
			// Get resources that are contained in the workspace
			IWorkspaceRoot workspaceRoot = PerforceCorePlugin.getWorkspace().getRoot();
			for (int i = 0; i < affectedFiles.length; i++)
			{
				try
				{
					IWhereResult whereResult = server.where(affectedFiles[i].getDepotFileName());
									
					IPath path = new Path(whereResult.getLocalFilename());
					IResource resource = workspaceRoot.getFileForLocation(path);
					if (resource != null && resource.exists())
					{
						result.add(resource);
					}					
				}
				catch (PerforceException e)
				{
					// Ignored!
				}
			}
		}
		catch (PerforceException e)
		{
			PerforceCorePlugin.log(e.getStatus());
		}
		
		return result;
	}
	
	/** Call this method to update the persistence cache with the information stored in the 
	  * given map (IResource -> ResourceSyncInfo).
	  * 
	  * @param resourceMap the map. 
	  */
	private void updateCache(Map resourceMap) 
	{
		if (resourceMap.isEmpty())
		{
			return;
		}
		
		try
		{
			PerforceSynchronizer.getInstance().beginOperation(null);
			
			for (Iterator it = resourceMap.keySet().iterator(); it.hasNext();)
			{
				IResource resource = (IResource) it.next();
				ResourceSyncInfo syncInfo =	(ResourceSyncInfo) resourceMap.get(resource);
					
				PerforceSynchronizer.getInstance().setResourceSync(resource, syncInfo);
			}						
		}
		catch (PerforceException e)
		{
			PerforceCorePlugin.log(e.getStatus());
		}
		finally
		{
			try
			{
				PerforceSynchronizer.getInstance().endOperation(null);
			}
			catch (PerforceException e)
			{
				PerforceCorePlugin.log(e.getStatus());
			}
		}
	}
	
	/** Collects resource that are open on the current client.
	  *
	  * @param server the server.
	  * @return set of resources. 
	  */
	private Set collectOpenedResources(IPerforceServer server)  
	{
		Set result = new HashSet();
		try
		{
			IOpenedResult[] openedResults = server.opened(false);

			// Collect depot filenames
			String[] filenames = new String[openedResults.length];
			for (int i = 0; i < openedResults.length; i++)
			{
				filenames[i] = openedResults[i].getDepotFilename();
			}
			
			// Get resources that are contained in the workspace
			IWorkspaceRoot workspaceRoot = PerforceCorePlugin.getWorkspace().getRoot();
			IWhereResult[] whereResults = server.where(filenames);
			for (int i = 0; i < whereResults.length; i++)
			{
				IPath path = new Path(whereResults[i].getLocalFilename());
				IResource resource = workspaceRoot.getFileForLocation(path);
				if (resource != null && resource.exists())
				{
					result.add(resource);
				}
			}
		}
		catch (PerforceException e)
		{
			int statusCode = e.getStatus().getCode();
			if (statusCode != 2010) // File's not in client view
			{
				PerforceCorePlugin.log(e.getStatus());
			}
		}
		
		return result;
	}
}
